﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using System.Xml;
using System.Xml.Linq;

namespace CreditAgricole.Framework
{
    public static class SPHelper
    {
        /// <summary>
        /// Get List from root site by its name
        /// </summary>
        /// <param name="siteId">Current site ID</param>
        /// <param name="listTitle">Title of the list to be get</param>
        /// <returns>SPList object with specified title, in the root web of the specified site</returns>
        public static SPList GetListByName(Guid siteId, string listTitle)
        {
            using (SPSite site = new SPSite(siteId))
            {
                using (SPWeb web = site.RootWeb)
                {                    
                    return web.Lists.TryGetList(listTitle);
                }
            }
        }

        /// <summary>
        /// Get List from root site by its name
        /// Called only from event receiver
        /// </summary>
        /// <param name="web">Web where to look for the list</param>
        /// <param name="listTitle">List title</param>
        /// <returns>SPlist with specified title in specified web if found</returns>
        public static SPList GetListByName(SPWeb web, string listTitle)
        {
            return web.Lists.TryGetList(listTitle);
        }

        /// <summary>
        /// Add new list based on specified template
        /// </summary>
        /// <param name="siteId">Site id where the list will be added</param>
        /// <param name="webId">web id where the list will be added</param>
        /// <param name="templateType">Base template</param>
        /// <param name="listTitle">New list title</param>
        /// <returns>SPList newly added</returns>
        public static SPList CreateListeBasedOnTemplate(Guid siteId, Guid webId, SPListTemplateType templateType, string listTitle)
        {
            using (SPSite site = new SPSite(siteId))
            {
                using (SPWeb web = site.OpenWeb(webId))
                {
                    Guid listGuid = web.Lists.Add(listTitle, string.Empty, templateType);
                    SPList addedList = web.Lists[listGuid];
                    addedList.OnQuickLaunch = false;
                    addedList.Update();
                    return addedList;   
                }
            }
        }

        /// <summary>
        /// Add new list based on specified template (no need to instanciate a new web becaue it is specified from event receiver properties)
        /// Called only from event receiver
        /// </summary>
        /// <param name="web">web where the list will be added</param>
        /// <param name="templateType">Base template</param>
        /// <param name="listTitle">New list title</param>
        /// <returns>SPList newly added</returns>
        public static SPList CreateListeBasedOnTemplate(SPWeb web, SPListTemplateType templateType, string listTitle)
        {
            Guid listGuid = web.Lists.Add(listTitle, string.Empty, templateType);
            SPList addedList = web.Lists[listGuid];
            addedList.OnQuickLaunch = false;
            addedList.Update();
            return addedList;
        }

        /// <summary>
        /// Check weather the item is already associated to a workflow
        /// </summary>
        /// <returns>boolean checking weather the item is already associated to a workflow</returns>
        public static bool IsWorkFlowAssociatedToItem(SPListItem item)
        {           
            bool isAssociated = false;
            if (item != null && item.Workflows.Count > 0)
            {
                isAssociated = true;
            }
            return isAssociated;
        }

        /// <summary>
        /// Assign the user to the workflow 
        /// </summary>
        /// <param name="associationData">XML doc containing workflow data </param>
        /// <param name="user">User to be assigned</param>
        /// <returns>Workflow Data xml after assigning the user</returns>
        public static string AssignUserToWorkflow(string associationData, List<SPFieldUserValue> approvers)
        {
            XNamespace d = "http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields";
            XNamespace pc = "http://schemas.microsoft.com/office/infopath/2007/PartnerControls";
            
            XDocument document = XDocument.Parse(associationData);
            var firstAssignment = document.Descendants(d + Constants.AssignmentElement).First();
            firstAssignment.Element(d + Constants.AssignmentTypeElement).Value = Constants.AssignmentTypeSerial;

            XElement assignee = firstAssignment.Element(d + Constants.AssigneeElement);
            assignee.RemoveNodes();
            if (approvers.Count > 0)
            {
                foreach (SPFieldUserValue approver in approvers)
                {
                    AddAssignee(approver, assignee, pc);
                    CALoggingHelper.AddInformationEntry(string.Format("User {0} associated to workflow.", approver.LookupValue));
                }
            }

            associationData = document.ToString(SaveOptions.DisableFormatting);
            CALoggingHelper.AddInformationEntry(associationData);
            return associationData;
        }

        /// <summary>
        /// Add assignee to the assignee collection workflow
        /// </summary>
        /// <param name="user">user to add</param>
        /// <param name="assignee">assignee collection</param>
        /// <param name="pc">Namspace related to assignee collection</param>
        private static void AddAssignee(SPFieldUserValue user, XElement assignee, XNamespace pc)
        {
            XElement person = new XElement(pc + Constants.PersonElement);
            XElement displayName = new XElement(pc + Constants.DisplayNameElement);
            XElement accountId = new XElement(pc + Constants.AccountIdElement);
            XElement accountType = new XElement(pc + Constants.AccountTypeElement);
            string groupType = string.Empty;

            if (user.User != null)
            {
                groupType = user.User.IsDomainGroup ? Constants.AccountTypeValueSecurityGroup : Constants.AccountTypeValueUser;
                displayName.Value = user.User.Name;
                accountId.Value = user.User.LoginName;
            }
            else
            {
                groupType = Constants.AccountTypeValueSPGroup;
                displayName.Value = user.LookupValue;
                accountId.Value = user.LookupValue;
            }

            accountType.Value = groupType;
            person.Add(displayName);
            person.Add(accountId);
            person.Add(accountType);
            assignee.Add(person);
        }

        /// <summary>
        /// Associate a workflow to the specified list
        /// </summary>
        /// <param name="siteId">Current Site Id</param>
        /// <param name="webId">Current web id</param>
        /// <param name="changingList">List that will have workflow associated with</param>
        /// <param name="historyList">History list required for the workflow</param>
        /// <param name="taskList">Task list required for the workflow</param>
        public static void AssociateWorkflowToList(SPWeb web, SPList changingList, SPList historyList, SPList taskList, string workflowTemplateName, string workflowAssociationName)
        {
            if (changingList.WorkflowAssociations.GetAssociationByName(workflowAssociationName, System.Globalization.CultureInfo.CurrentCulture) == null)
            {
                bool allowUnsafeUpdates = web.AllowUnsafeUpdates;
                web.AllowUnsafeUpdates = true;
                SPWorkflowTemplate workflowTemplate = web.WorkflowTemplates.GetTemplateByName(workflowTemplateName, System.Globalization.CultureInfo.CurrentCulture);
                SPWorkflowAssociation workflowAssociation = SPWorkflowAssociation.CreateListAssociation(workflowTemplate, workflowAssociationName, taskList, historyList);
                workflowAssociation.AllowManual = false;
                workflowAssociation.AutoStartCreate = false;
                workflowAssociation.AutoStartChange = false;

                // Change default properties
                XNamespace d = "http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields";
                XNamespace pc = "http://schemas.microsoft.com/office/infopath/2007/PartnerControls";

                XDocument document = XDocument.Parse(workflowAssociation.AssociationData);

                var CancelOnRejection = document.Descendants(d + "CancelonRejection").First();
                if (CancelOnRejection != null) CancelOnRejection.Value = "true"; else CALoggingHelper.AddWarningEntry("Workflow AssociationData : CancelonRejection node not found.");

                var CancelonChange = document.Descendants(d + "CancelonChange").First();
                if (CancelonChange != null) CancelonChange.Value = "true"; else CALoggingHelper.AddWarningEntry("Workflow AssociationData : CancelonChange node not found.");

                var EnableContentApproval = document.Descendants(d + "EnableContentApproval").First();
                if (EnableContentApproval != null) EnableContentApproval.Value = "true"; else CALoggingHelper.AddWarningEntry("Workflow AssociationData : EnableContentApproval node not found.");

                workflowAssociation.AssociationData = document.ToString(SaveOptions.DisableFormatting);

                changingList.WorkflowAssociations.Add(workflowAssociation);
                changingList.Update();

                CALoggingHelper.AddInformationEntry(string.Format("Workflow associated to {0} list.", changingList.Title));
                web.AllowUnsafeUpdates = allowUnsafeUpdates;
            }
        }

        ///// <summary>
        ///// Add event receiver to start workflow when item is added to the specified list
        ///// </summary>
        ///// <param name="changingList">List</param>
        //private static void AddEventReceiversToList(SPList changingList)
        //{            
        //    //changingList.EventReceivers.Add(SPEventReceiverType.ItemUpdated,
        //    //                                "CreditAgricole.Application, Version=1.0.0.0, Culture=neutral, PublicKeyToken=335131159ffe7a0c",
        //    //                                "CreditAgricole.Application.StartWFEventReceiver");
        //}

        /// <summary>
        /// This method search for SPGroup name.
        /// </summary>
        /// <param name="group">string</param>
        /// <returns>SPGroup object</returns>
        public static SPGroup SearchGroup(string groupName, SPWeb web)
        {
            if (web == null) return null;

            SPGroup groupObject = null;

            foreach (SPGroup singleGroup in web.SiteGroups)
            {
                if (groupName == singleGroup.Name)
                {
                    groupObject = singleGroup;
                }
            }
            return groupObject;
        }

        public static void HideField(SPList list, string fieldInternalName)
        {
            SPField titleField = null;

            try
            {
                titleField = list.Fields.GetFieldByInternalName(fieldInternalName);
            }
            catch { }

            if (titleField != null)
            {                
                titleField.Hidden = true;
                titleField.Update(true);
                list.Update();
                CALoggingHelper.AddInformationEntry(string.Format("The {0} field of the \"{1}\" list is now hidden.", fieldInternalName, list.Title));
            }
            else
            {
                CALoggingHelper.AddWarningEntry(string.Format("The {0} field of the \"{1}\" list cannot be found and set as hidden.", fieldInternalName, list.Title));
            }
        }

        public static void SetItemReadOnly(SPListItem item)
        {
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                if (item != null)
                {
                    item.BreakRoleInheritance(false);
                    SPRoleDefinition role = item.Web.RoleDefinitions.GetByType(SPRoleType.Reader);
                    SPRoleAssignment assignment = new SPRoleAssignment(item.Web.AssociatedVisitorGroup);
                    assignment.RoleDefinitionBindings.Add(role);
                    item.RoleAssignments.Add(assignment);
                }
            });
        }

        public static SPUser GetCurrentApprover(SPListItem item)
        {
            if (item.Tasks.Count > 0)
            {
                SPWorkflowTask task = item.Tasks[0];
                if ((bool)task[SPBuiltInFieldId.Completed])
                    return null;
                else
                    return GetAssignedUser(task);
            }
            else
                return null;
        }

        public static string GetCurrentApproverName(SPListItem item)
        {
            SPUser usr = GetCurrentApprover(item);
            return usr == null ? string.Empty : usr.Name;
        }

        public static SPUser GetLastApprover(SPListItem item)
        {
            if (item.Tasks.Count > 0)
            {
                for (int i = 0; i < item.Tasks.Count; i++)
                {
                    // Get last completed task
                    SPWorkflowTask task = item.Tasks[i];
                    if ((bool)task[SPBuiltInFieldId.Completed])
                    {                        
                        return GetAssignedUser(task);
                    }
                }
                return null;
            }
            else
                return null;
        }

        public static string GetLastApproverName(SPListItem item)
        {
            SPUser usr = GetLastApprover(item);
            return usr == null ? string.Empty : usr.Name;
        }

        public static SPUser GetAssignedUser(SPWorkflowTask task)
        {
            if (task == null) 
                return null;
            else
            {
                SPFieldUser u = task.Fields[SPBuiltInFieldId.AssignedTo] as SPFieldUser;
                if (u != null)
                {
                    SPFieldUserValue uv = u.GetFieldValue((string)task[SPBuiltInFieldId.AssignedTo]) as SPFieldUserValue;
                    if (uv != null)
                    {
                        return uv.User;
                    }
                    else
                        return null;
                }
                else
                {
                    return null;
                }
            }
        }

        public static string GetAssignedUserName(SPWorkflowTask task)
        {
            SPUser usr = GetAssignedUser(task);
            return usr == null ? string.Empty : usr.Name;
        }

        public static Guid GetWorkflowStatusFieldId(SPListItem item)
        {
            try
            {
                SPFieldCollection fields = item.Fields;
                foreach (SPField f in fields)
                {
                    if (f.Type == SPFieldType.WorkflowStatus) return f.Id;
                }
                return Guid.Empty;
            }
            catch 
            {
                return Guid.Empty;
            }

        }

        private static void RotateContentTypeOrder(SPList list)
        {
            if (list.RootFolder.ContentTypeOrder.Count < 2) return; // No rotation possible if not at least 2 elements

            IList<SPContentType> ctl = list.RootFolder.ContentTypeOrder;
            SPContentType tmp = ctl[ctl.Count - 1]; // Get the last item
            ctl.RemoveAt(ctl.Count - 1); // Remove it
            ctl.Insert(0, tmp); // and the reinsert it at the beginning of the list

            list.RootFolder.UniqueContentTypeOrder = ctl;
            list.Update();
        }

        public static string GetChoiceFieldDisplayText(SPFieldChoice choiceField, object choiceFieldValue)
        {
            try
            {
                return choiceField.GetFieldValueAsText(choiceFieldValue);
            }
            catch
            {
                return string.Empty;
            }
        }

        public static string GetChoiceFieldDisplayText(SPListItem item, string choiceFieldName)
        {
            try
            {
                SPFieldChoice fc = item.Fields[choiceFieldName] as SPFieldChoice;
                return fc.GetFieldValueAsText(item[choiceFieldName]);
            }
            catch
            {
                return string.Empty;
            }
        }

        public static string GetChoiceFieldDisplayText(SPListItem item, Guid choiceFieldId)
        {
            try
            {
                SPFieldChoice fc = item.Fields[choiceFieldId] as SPFieldChoice;
                return fc.GetFieldValueAsText(item[choiceFieldId]);
            }
            catch
            {
                return string.Empty;
            }
        }

        public static string GetWorkflowFieldDisplayText(SPListItem item)
        {
            return GetChoiceFieldDisplayText(item, GetWorkflowStatusFieldId(item));
        }

        public static string GetInternalFieldName(SPList list, Guid fieldId)
        {
            return list.Fields[fieldId].InternalName;
        }

        public static string GetInternalFieldName(SPList list, string displayName)
        {
            string internalFieldName = string.Empty;
            try
            {
                if (list.Fields[displayName] != null)
                {
                    internalFieldName = list.Fields[displayName].InternalName;
                }
            }
            catch (ArgumentException argEx)
            {
                CALoggingHelper.AddErrorEntry(argEx.StackTrace);
            }
            return internalFieldName;
        }

        public static void EnforceUniqueValue(SPWeb web, string ListName, Guid FieldId)
        {
            // Get List instance to set EnforceUniqueValue on Title field (cannot be done using schema.xml)
            try
            {                
                if (web != null)
                {
                    SPList list = web.Lists.TryGetList(ListName);
                    if (list != null)
                    {
                        SPField fld = list.Fields[FieldId];
                        fld.Indexed = true;
                        fld.EnforceUniqueValues = true;
                        fld.Update();
                        list.Update();
                    }
                }
            }
            catch (Exception ex)
            {
                CALoggingHelper.AddErrorEntry(ex.Message);
            }
        }

        /// <summary>
        /// Add assignees to workflow and start it immediately, if no workflow is already associated to the item
        /// </summary>
        /// <param name="item">Item where the workflow will be started</param>       
        public static void StartWorkflow(SPListItem item)
        {
            StartWorkflow(item, false, 0);
        }

        /// <summary>
        /// Add assignees to workflow and start it immediately, if no workflow is already associated to the item
        /// </summary>
        /// <param name="item">Item where the workflow will be started</param>
        /// <param name="workflowAssociationIndex">Index of the workflowAssociation in the list WorlFlowAssociations collection</param>
        public static void StartWorkflow(SPListItem item, int workflowAssociationIndex)
        {
            StartWorkflow(item, false, workflowAssociationIndex);
        }

        /// <summary>
        /// Add assignees to workflow and start it immediately 
        /// </summary>
        /// <param name="item">Item where the workflow will be started</param>
        /// <param name="bypassItemAssociationCheck">False to check if there's already an associated workflow before starting</param>
        public static void StartWorkflow(SPListItem item, bool bypassItemAssociationCheck)
        {
            StartWorkflow(item, bypassItemAssociationCheck, 0);
        }

        /// <summary>
        /// Add assignees to workflow and start it immediately 
        /// </summary>
        /// <param name="item">Item where the workflow will be started</param>
        /// <param name="bypassItemAssociationCheck">False to check if there's already an associated workflow before starting</param>
        /// <param name="workflowAssociationIndex">Index of the workflowAssociation in the list WorlFlowAssociations collection</param>
        public static void StartWorkflow(SPListItem item, bool bypassItemAssociationCheck, int workflowAssociationIndex)
        {
            if (item != null && item.File != null && item.File.CheckOutType == SPFile.SPCheckOutType.None && (bypassItemAssociationCheck || !SPHelper.IsWorkFlowAssociatedToItem(item)))
            {
                if (item[Constants.FirstApproverField] != null)
                {
                    List<SPFieldUserValue> users = new List<SPFieldUserValue>();
                    users.Add(GetSPUser(item, Constants.FirstApproverField));
                    CALoggingHelper.AddInformationEntry(string.Format("{0} is {1}.", Constants.FirstApproverField, users[users.Count - 1].User.Name));
                    if (item[Constants.SecondApproverField] != null)
                    {
                        users.Add(GetSPUser(item, Constants.SecondApproverField));
                        CALoggingHelper.AddInformationEntry(string.Format("{0} is {1}.", Constants.SecondApproverField, users[users.Count - 1].User.Name));
                    }                    
                    SPWorkflowAssociation workflowAssociation = item.ParentList.WorkflowAssociations[workflowAssociationIndex];
                    workflowAssociation.AssociationData = SPHelper.AssignUserToWorkflow(workflowAssociation.AssociationData, users);
                    item.ParentList.ParentWeb.Site.WorkflowManager.StartWorkflow(item, workflowAssociation, workflowAssociation.AssociationData, true);
                    CALoggingHelper.AddInformationEntry("Workflow started.");
                }
            }
        }

        /// <summary>
        /// Get the SPUser object from Item 
        /// </summary>
        /// <param name="item">Item from the list</param>
        /// <param name="fieldName">field name containing the user</param>
        /// <returns>SPUser object of the specified item</returns>
        public static SPFieldUserValue GetSPUser(SPListItem item, string fieldName)
        {
            SPFieldUser field = item.Fields.GetField(fieldName) as SPFieldUser;
            if (field != null && item[fieldName] != null)
            {
                SPFieldUserValue fieldValue = field.GetFieldValue(MappingTool.CatchNull(item[fieldName])) as SPFieldUserValue;
                if (fieldValue != null)
                {
                    return fieldValue;
                }
            }
            return null;
        }
    }
}
